package controllers

import akka.stream.Materializer
import command.CommandExec
import dao._
import play.api.mvc._
import tool._

import java.io.File
import java.nio.file.Files
import javax.inject.Inject
import scala.concurrent.ExecutionContext.Implicits.global
import implicits.Implicits._
import play.api.data.{Form, FormUtils}
import play.api.http.HttpVerbs
import play.api.i18n.I18nSupport
import play.api.libs.ws.WSClient
import scalax.collection.GraphPredef.EdgeAssoc
import scalax.collection.mutable.Graph
import shared.pojo.MissionTool._
import shared.pojo.Pojo.ValidData
import shared.pojo.WorkflowMission.FsData
import shared.pojo.mission._
import tool.Pojo.{CommandData, MissionIdData}
import tool.plot.{RocCv, RocCvEach}
import utils.Utils
import upickle.default._

/** Created by yz on 4/12/2020
  */
class MissionToolController @Inject() (cc: ControllerComponents)(
    implicit val missionDao: MissionDao,
    implicit val workflowMissionDao: WorkflowMissionDao,
    implicit val ws: WSClient,
    implicit val materializer: Materializer
) extends AbstractController(cc)
    with WorkflowMissionFileTool
    with I18nSupport {

  def getWorkspaceImage = Action.async { implicit request =>
    val userId = Tool.getUserId
    val missionId = FormTool.missionIdForm.bindFromRequest().get.missionId
    val fileName = FormTool.fileNameForm.bindFromRequest().get.fileName
    val idStrOp = FeatureSelectionTool.getIdStrOp(fileName)
    val workspaceF = Tool.getWorkspaceDirF(idStrOp, missionId)
    workspaceF.map { workspaceDir =>
      val file = new File(workspaceDir, fileName)
      val ifModifiedSinceStr = request.headers.get(IF_MODIFIED_SINCE)
      val trueFile = if (file.exists()) {
        file
      } else {
        Tool.getDefaultAvatarFile
      }
      val lastModifiedStr = trueFile.lastModified().toString
      val MimeType = "image/png"
      val byteArray = Files.readAllBytes(trueFile.toPath)
      if (ifModifiedSinceStr.isDefined && ifModifiedSinceStr.get == lastModifiedStr) {
        NotModified
      } else {
        Ok(byteArray).as(MimeType).withHeaders(LAST_MODIFIED -> trueFile.lastModified().toString)
      }
    }
  }

  def workspaceImage = Action.async { implicit request =>
    val data = Tool.transformRq.transform(reader[GetFileData.Request])
    val idStrOp = FeatureSelectionTool.getIdStrOp(data.fileName)
    val workspaceF = Tool.getWorkspaceDirF(idStrOp, data.id.toInt)
    workspaceF.map { workspaceDir =>
      val file = new File(workspaceDir, data.fileName)
      val ifModifiedSinceStr = request.headers.get(IF_MODIFIED_SINCE)
      val trueFile = if (file.exists()) {
        file
      } else {
        Tool.getDefaultAvatarFile
      }
      val lastModifiedStr = trueFile.lastModified().toString
      val MimeType = "image/png"
      val byteArray = Files.readAllBytes(trueFile.toPath)
      if (ifModifiedSinceStr.isDefined && ifModifiedSinceStr.get == lastModifiedStr) {
        NotModified
      } else {
        Ok(byteArray).as(MimeType).withHeaders(LAST_MODIFIED -> trueFile.lastModified().toString)
      }
    }
  }

  def getConfigData = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    val missionId = data.missionId
    val fileNameOp = FormTool.fileNameOpForm.bindFromRequest().get.fileName
    val idStrOp = FeatureSelectionTool.getIdStrOp(fileNameOp)
    val workspaceF = Tool.getWorkspaceDirF(idStrOp, missionId)
    workspaceF.map { workspaceDir =>
      val fileName = fileNameOp.getOrElse("config.txt")
      val configFile = new File(workspaceDir, fileName)
      val map = configFile.txtLines
        .filter(_.size == 2)
        .map { columns =>
          val value = if (columns(1) == "NA") "" else columns(1)
          (columns(0) -> value)
        }
        .toMap
      Ok(writeJs(map))
    }
  }

  def plotConfigData = Action.async { implicit request =>
    val data = Tool.transformRq.transform(reader[PlotConfigDataRequest])
    val idStrOp = data.idStr.toOption
    val workspaceF = Tool.getWorkspaceDirF(idStrOp, data.id)
    workspaceF.map { workspaceDir =>
      val inDir = new File(workspaceDir, data.idStr)
      val file = new File(inDir, s"${data.fileName}")
      Ok(ujson.read(file))
    }
  }

  def getConfigJsonData = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    val missionId = data.missionId
    val fileNameOp = FormTool.fileNameOpForm.bindFromRequest().get.fileName
    val formIdStrOp = FormTool.idStrOpForm.bindFromRequest().get.idStr
    val idStrOp = if (formIdStrOp.isDefined) {
      formIdStrOp
    } else {
      FeatureSelectionTool.getIdStrOp(fileNameOp)
    }
    val workspaceF = Tool.getWorkspaceDirF(idStrOp, missionId)
    workspaceF.map { workspaceDir =>
      val fileName = fileNameOp.get
      val configFile = new File(workspaceDir, fileName)
      val json = Utils.configFile2JsonByFile(configFile)
      Ok(json)
    }
  }

  def plot = Action.async { implicit request =>
    val missionId = FormTool.missionIdForm.bindFromRequest().get.missionId
    val data = SFormTool.getAllPlotData
    val kind = FormTool.kindForm.bindFromRequest().get.kind
    val extraData = FormTool.extraDataForm.bindFromRequest().get
    val softKindOp = FormTool.softKindOpForm.bindFromRequest().get.softKind
    val idStrOp = FormTool.idStrOpForm.bindFromRequest().get.idStr
    val workspaceResultDirF = Tool.getWorkspaceResultDirF(idStrOp, missionId)
    workspaceResultDirF.map { case (workspaceDir, resultDir) =>
      val rPlot = Tool.kindRPlotMap(kind)
      val execWorkspaceDir = idStrOp
        .map { idStr =>
          val cvNames = List(RocCv, RocCvEach).map(_.name)
          if (cvNames.contains(rPlot.name)) {
            new File(workspaceDir, s"${idStr}/${extraData.prefix.toLowerCase}/cv")
          } else {
            new File(workspaceDir, s"${idStr}/${extraData.prefix.toLowerCase}")
          }
        }
        .getOrElse {
          workspaceDir
        }
      rPlot.producePlotConfigFile(data, execWorkspaceDir, extraData)
      val execCommand = CommandExec()
        .exec { b =>
          val command = rPlot.getCommand(data, extraData)
          CommandData(execWorkspaceDir, List(command), s"${rPlot.name}_plot.sh")
        }
        .map { b =>
          idStrOp
            .map { idStr =>
              val value = Utils.configFile2Json(workspaceDir)
              val data = value.transform(reader[FsData])
              val graphJson = ujson.read(data.graphData)
              val lines = graphJson("pens").arr.filter { x =>
                x("type").num == 1
              }
              val froms = lines.map { node =>
                node("from")("id").str
              }
              val tos = lines.map { node =>
                node("to")("id").str
              }
              val edges = froms
                .zip(tos)
                .map { case (from, to) =>
                  from ~> to
                }
                .toList
              val graph = Graph(edges: _*)
              Tool.featureSelectionResultDeal(workspaceDir, resultDir, graph = graph)
            }
            .getOrElse {
              softKindOp
                .map { softKind =>
                  val curSoft = Tool.kindSoftMap(softKind)
                  curSoft.resultDeal(workspaceDir, resultDir)
                }
                .getOrElse {
                  Tool.resultDeal(workspaceDir, resultDir)
                }
            }
        }
      if (execCommand.isSuccess) {
        Ok(ujson.Obj("valid" -> true))
      } else {
        Ok(ujson.Obj("valid" -> false, "message" -> execCommand.errorInfo))
      }
    }
  }

  def myPlot = Action.async { implicit request =>
    implicit val configJson = Tool.transformRq
    val plotKindData = configJson.transform(reader[PlotKindData])
    val idStrOp = plotKindData.idStr.toOption
    val workspaceResultDirF = Tool.getWorkspaceResultDirF(idStrOp, plotKindData.id)
    workspaceResultDirF.map { case (workspaceDir, resultDir) =>
      val rPlot = Tool.kindMyRPlotMap(plotKindData.kind)
      rPlot.producePlotConfigFile(workspaceDir)
      val execWorkspaceDir =
        rPlot.getWorkspaceDir(workspaceDir, idStr = plotKindData.idStr, prefix = plotKindData.prefix)
      val execCommand = CommandExec()
        .exec { b =>
          implicit val wd = workspaceDir
          val command = rPlot.getCommand
          val shFileName = rPlot.shFileName
          CommandData(execWorkspaceDir, List(command), shFileName)
        }
        .map { b =>
          idStrOp
            .map { idStr =>
              val value = Utils.configFile2Json(workspaceDir)
              val data = value.transform(reader[FsData])
              val graphJson = ujson.read(data.graphData)
              val lines = graphJson("pens").arr.filter { x =>
                x("type").num == 1
              }
              val froms = lines.map { node =>
                node("from")("id").str
              }
              val tos = lines.map { node =>
                node("to")("id").str
              }
              val edges = froms
                .zip(tos)
                .map { case (from, to) =>
                  from ~> to
                }
                .toList
              val graph = Graph(edges: _*)
              Tool.featureSelectionResultDeal(workspaceDir, resultDir, graph = graph)
            }
            .getOrElse {
              Tool.resultDeal(workspaceDir, resultDir)
            }
        }
      val response = if (execCommand.isSuccess) {
        ValidData(valid = true)
      } else {
        ValidData(valid = false, message = execCommand.errorInfo)
      }
      Ok(writeJs(response))
    }
  }

  def getColorData = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    val kindOp = FormTool.kindOpForm.bindFromRequest().get.kind
    val idStrOp = FormTool.idStrOpForm.bindFromRequest().get.idStr
    val missionId = data.missionId
    val workspaceF = Tool.getWorkspaceDirF(idStrOp, missionId)
    workspaceF.map { workspaceDir =>
      val execWorkspaceDir = idStrOp
        .map { idStr =>
          new File(workspaceDir, idStr)
        }
        .getOrElse {
          workspaceDir
        }
      val colorFile = kindOp
        .map { kind =>
          Tool.getSampleColorFile(execWorkspaceDir, kind)
        }
        .getOrElse(Tool.getSampleColorFile(execWorkspaceDir))
      val map = colorFile.txtLines
        .filter(_.size == 2)
        .drop(1)
        .map { columns =>
          (columns(0) -> columns(1))
        }
        .toSeqMap
      Ok(writeJs(map))
    }
  }

  def getHeaders = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    val fileName = FormTool.fileNameForm.bindFromRequest().get.fileName
    val missionId = data.missionId
    val idStrOp = FeatureSelectionTool.getIdStrOp(fileName)
    val workspaceF = Tool.getWorkspaceDirF(idStrOp, missionId)
    workspaceF.map { workspaceDir =>
      val file = new File(workspaceDir, fileName)
      val xs = file.csvLines.head.drop(1)
      Ok(writeJs(xs))
    }
  }

  def getGroups = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    val idStrOp = FormTool.idStrOpForm.bindFromRequest().get.idStr
    val missionId = data.missionId
    val workspaceF = Tool.getWorkspaceDirF(idStrOp, missionId)
    workspaceF.map { workspaceDir =>
      val groupFile = Tool.getRawGroupFile(workspaceDir)
      val groups = groupFile.txtLines.lineSeqMapLower.map { map =>
        map("classnote")
      }.distinct
      Ok(writeJs(groups))
    }
  }

  def getRowCols = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    val idStrOp = FormTool.idStrOpForm.bindFromRequest().get.idStr
    val missionId = data.missionId
    val workspaceF = Tool.getWorkspaceDirF(idStrOp, missionId)
    workspaceF.map { workspaceDir =>
      val dataFile = new File(workspaceDir, "dataFileNode/Diff_Metabolite.csv")
      val lines = dataFile.csvLines
      val columnNames = lines.head.drop(1)
      val rowNames = lines.map(_.head).drop(1)
      Ok(ujson.Obj("headers" -> columnNames, "rowNames" -> rowNames))
    }
  }

  def getNumericColNames = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    val idStrOp = FormTool.idStrOpForm.bindFromRequest().get.idStr
    val missionId = data.missionId
    val workspaceF = Tool.getWorkspaceDirF(idStrOp, missionId)
    workspaceF.map { workspaceDir =>
      val dataFile = new File(workspaceDir, "group.txt")
      val lines = dataFile.txtLines
      val columnNames = lines.head.drop(1)
      val filterColumnNames = columnNames.filter { columnName =>
        val columns = lines.selectOneColumn(columnName)
        columns.forall(_.isDouble)
      }
      Ok(ujson.Obj("headers" -> filterColumnNames))
    }
  }

  def checkFileExist = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    val fileName = FormTool.fileNameForm.bindFromRequest().get.fileName
    val idStrOp = FeatureSelectionTool.getIdStrOp(fileName)
    val workspaceF = Tool.getWorkspaceDirF(idStrOp, data.missionId)
    workspaceF.map { workspaceDir =>
      val file = new File(workspaceDir, fileName)
      Ok(writeJs(file.exists()))
    }
  }

  def downloadWorkspaceFile = Action.async { implicit request =>
    val data = Tool.transformRq.transform(reader[GetFileData.Request])
    val idStrOp = FeatureSelectionTool.getIdStrOp(data.fileName)
    val workspaceF = Tool.getWorkspaceDirF(idStrOp, data.id.toInt)
    workspaceF.map { workspaceDir =>
      val file = new File(workspaceDir, data.fileName)
      Ok.sendFile(file)
        .withHeaders(
          CONTENT_DISPOSITION -> Tool.getContentDisposition(s"${file.getName}"),
          CONTENT_TYPE -> "application/x-download"
        )
    }
  }

  def isPaired = Action { implicit request =>
    val data = Tool.transformRq.transform(reader[IsPaired.Request])
    val userId = Tool.getUserId
    val groupFile = Tool.getGroupFile(userId, data.groupFile)
    val dataLines = Utils.execFuture(Tool.produceRawDataLinesF(userId, data.file))
    val sampleNames = dataLines.head.drop(1)
    val lines = groupFile.txtLines
    val maps = lines.lineSeqMapLower
      .filter { map =>
        data.groups.contains(map("classnote"))
      }
      .filter { map =>
        sampleNames.contains(map("sample"))
      }
    val hasPairID = groupFile.txtLines.head.containsIgnoreCase("Pair_ID")
    val b = if (hasPairID) {
      val groups = maps.groupBy { map =>
        map("classnote")
      }
      val hasSampleSize = groups.forall { case (classnote, inMaps) =>
        (inMaps.size / maps.size.toDouble) == 0.5
      }
      val isUniq = groups.forall { case (classnote, inMaps) =>
        inMaps.map(_("pair_id")).isUniq
      }
      val sets = groups.map { case (classnote, inMaps) =>
        inMaps.map(_("pair_id")).toSet
      }
      val isSame = sets.forall { set =>
        set == sets.head
      }
      hasSampleSize && isUniq && isSame
    } else false
    Ok(writeJs(b))
  }

}
